package com.kermel.andromeda.data;

import java.util.List;

import android.content.Context;
import android.test.AndroidTestCase;

public class AlarmManagerTest extends AndroidTestCase {
    public static final String ALARM_NAME_FIXTURE = "AlarmNameFixture";
    public static final String LAST_ALARM_NAME = "LastAlarm";
    public static final int DEFAULT_NUMBER_OF_ALARMS = 10;
    public static final int MEDIUM_NUMBER_OF_ALARMS = 100;
    public static final int LARGE_NUMBER_OF_ALARMS = 1000;
    public static final int EXTRA_LARGE_NUMBER_OF_ALARMS = 10000;
    public static final int A_SINGLE_ALARM = 1;
    
    private AlarmManager alarmManager;
    
    protected void setUp() throws Exception {
        super.setUp();
        
        alarmManager = AlarmManager.instance;
        alarmManager.initAlarmsList();
    }

    protected void tearDown() throws Exception {
        alarmManager.removeAllAlarms();
        alarmManager = null;
        
        super.tearDown();
    }
    
    private void createAndAddAlarms(int numAlarms, boolean enabled) {
        Alarm alarm = null;
        
        for (int i = 0; i < numAlarms; i++) {
            alarm = new Alarm();
            alarm.name = buildAlarmName(i);
            alarm.enabled = enabled;
            alarmManager.addAlarm(alarm, getContext());
        }
    }
    
    private Alarm createAndAddSingleAlarm() {
        Alarm alarm = createAlarm();
        alarmManager.addAlarm(alarm, getContext());
        return alarm;
    }
    
    private Alarm createAlarm() {
        Alarm alarm = new Alarm();
        alarm.name = buildAlarmName(0);
        return alarm;
    }
    
    private String buildAlarmName(int index) {
        String alarmName = String.format("%s %d", ALARM_NAME_FIXTURE, index);
        return alarmName;
    }
    
    private void removeStatusNotification(Alarm alarm) {
        if (alarm != null) {
            int alarmNotificationID = alarm.getAlarmNotificationID();
            alarmManager.alarmScheduler.removeStatusNotification(alarmNotificationID);
        }
    }

    private boolean adjustAlarmToRetrigger(Alarm alarm) {
        Context context = getContext();
        
        boolean wasAdjusted = alarmManager.adjustAlarmToRetriggerAtEndOfSnoozePeriod(alarm, context);
        removeStatusNotification(alarm);
        
        return wasAdjusted;
    }
    
    public void testAddAlarmWithNullAlarm() {
        try {
            alarmManager.addAlarm(null, getContext());
            int numAlarms = alarmManager.getAlarmCount();
            assertEquals(0, numAlarms);
        } catch (Exception e) {
            fail("Exception thrown when adding a null alarm");
        }
    }
    
    public void testAddAlarmWithNonNullAlarm() {
        Alarm alarm = new Alarm();
        alarm.name = ALARM_NAME_FIXTURE;

        alarmManager.addAlarm(alarm, getContext());

        int numAlarms = alarmManager.getAlarmCount();
        assertEquals(1, numAlarms);
    }
    
    public void testThatAllAlarmsAreAdded() {
        createAndAddAlarms(DEFAULT_NUMBER_OF_ALARMS, true);
        assertEquals(DEFAULT_NUMBER_OF_ALARMS, alarmManager.getAlarmCount());
    }
    
    public void testAreAnyAlarmsSetWhenThereAreNoAlarmsThatHaveBeenSet() {
        assertFalse(alarmManager.areAnyAlarmsSet());
    }
    
    public void testThatAreAnyAlarmsSetReturnsFalseWhenNoAlarmsHaveBeenEnabled() {
        createAndAddAlarms(DEFAULT_NUMBER_OF_ALARMS, false);
        assertFalse(alarmManager.areAnyAlarmsSet());
    }
    
    // This test actually invokes the TriggeredAlarmActivity; might be able to use this to find a way to add a test that determines if the alarm was triggered.
    public void testThatAreAnyAlarmsSetReturnsTrueWhenAtLeastOneAlarmHasBeenEnabled() {
        createAndAddAlarms(DEFAULT_NUMBER_OF_ALARMS, false);
        
        Alarm alarm = alarmManager.alarms.get(0);
        assertNotNull(alarm);
        
        alarm.enabled = true;
        
        assertTrue(alarmManager.areAnyAlarmsSet());
    }
    
    public void testThatGetAlarmsReturnsNonNullByDefault() {
        List<Alarm> alarms = alarmManager.getAlarms();
        assertNotNull(alarms);
    }
    
    public void testThatGetAlarmsReturnsEmptyByDefault() {
        List<Alarm> alarms = alarmManager.getAlarms();
        assertTrue(alarms.isEmpty());
    }
    
    public void testThatGetAlarmsReturnsNonNullList() {
        createAndAddAlarms(A_SINGLE_ALARM, false);
        List<Alarm> alarms = alarmManager.getAlarms();
        assertNotNull(alarms);
    }
    
    public void testThatGetAlarmsReturnsNonEmptyList() {
        createAndAddAlarms(A_SINGLE_ALARM, false);
        List<Alarm> alarms = alarmManager.getAlarms();
        assertFalse(alarms.isEmpty());
    }
    
    public void testThatGetAlarmsReturnsAnAccurateList() {
        Alarm alarmFixture = new Alarm();
        alarmFixture.name = ALARM_NAME_FIXTURE;
        alarmManager.addAlarm(alarmFixture, getContext());
        
        List<Alarm> alarms = alarmManager.getAlarms();
        Alarm alarm = alarms.get(0);
        assertNotNull(alarm);
        assertEquals(alarmFixture, alarm);
    }
    
    public void testThatGetAlarmByIndexReturnsNullWhenAlarmsListIsEmpty() {
        Alarm retrievedAlarm = alarmManager.getAlarmByIndex(0);
        assertNull(retrievedAlarm);
    }
    
    public void testThatGetAlarmByIndexReturnsNullWhenIndexIsLessThanZero() {
        createAndAddAlarms(A_SINGLE_ALARM, false);
        Alarm retrievedAlarm = alarmManager.getAlarmByIndex(-1);
        assertNull(retrievedAlarm);
    }
    
    public void testThatGetAlarmByIndexReturnsNullWhenIndexIsGreaterThanNumberOfAlarmsInListUsingAnEmptyList() {
        Alarm retrievedAlarm = alarmManager.getAlarmByIndex(0);
        assertNull(retrievedAlarm);
    }
    
    public void testThatGetAlarmByIndexReturnsNullWhenIndexIsGreaterThanNumberOfAlarmsInListUsingNonEmptyList() {
        createAndAddAlarms(DEFAULT_NUMBER_OF_ALARMS, false);

        int index = DEFAULT_NUMBER_OF_ALARMS;
        Alarm retrievedAlarm = alarmManager.getAlarmByIndex(index);
        
        assertNull(retrievedAlarm);
    }
    
    public void testThatGetAlarmByIndexReturnsFirstItem() {
        Alarm alarm = createAndAddSingleAlarm();
        
        int index = 0;
        Alarm retrievedAlarm = alarmManager.getAlarmByIndex(index);
        
        assertNotNull(retrievedAlarm);
        assertEquals(alarm, retrievedAlarm);
    }
    
    public void testThatGetAlarmByIndexReturnsLastItem() {
        int numAlarms = DEFAULT_NUMBER_OF_ALARMS;
        createAndAddAlarms(numAlarms, false);
        
        Alarm lastAlarm = new Alarm();
        lastAlarm.name = LAST_ALARM_NAME;
        alarmManager.addAlarm(lastAlarm, getContext());
        numAlarms++;
        
        int index = numAlarms - 1;
        Alarm retrievedAlarm = alarmManager.getAlarmByIndex(index);
        
        assertNotNull(retrievedAlarm);
        assertEquals(lastAlarm, retrievedAlarm);
    }
    
    public void testThatIsValidIndexReturnsFalseWithEmptyList() {
        int index = 0;
        boolean result = alarmManager.isValidIndex(index);
        assertFalse(result);
    }
    
    public void testThatIsValidIndexReturnsFalseWhenIndexIsLessThanZero() {
        createAndAddSingleAlarm();
        
        int index = -1;
        boolean result = alarmManager.isValidIndex(index);
        
        assertFalse(result);
    }
    
    public void testThatIsValidIndexReturnsFalseWhenIndexIsGreaterThanNumberOfAlarmsInList() {
        createAndAddSingleAlarm();
        
        int index = 1;
        boolean result = alarmManager.isValidIndex(index);
        
        assertFalse(result);
    }
    
    public void testThatIsValidIndexReturnsTrueWhenIndexIsZeroUsingNonEmptyList() {
        createAndAddSingleAlarm();

        int index = 0;
        boolean result = alarmManager.isValidIndex(index);
        
        assertTrue(result);
    }
    
    public void testThatIsValidIndexReturnsTrueWhenIndexRefersToLastAlarmInList() {
        int numAlarms = DEFAULT_NUMBER_OF_ALARMS;
        createAndAddAlarms(numAlarms, false);
        int index = numAlarms - 1;
        boolean result = alarmManager.isValidIndex(index);
        assertTrue(result);
    }
    
    public void testThatRemoveAlarmReturnsFalseWhenTryingToRemoveNullAlarm() {
        try {
            boolean result = alarmManager.removeAlarm(null);
            assertFalse(result);
        } catch (Exception e) {
            fail("Exception thrown when removing a null alarm");
        }
    }
    
    public void testThatRemoveAlarmReturnsFalseWhenTryingToRemoveAnAlarmFromAnEmptyList() {
        Alarm alarm = new Alarm();
        boolean result = alarmManager.removeAlarm(alarm);
        assertFalse(result);
    }
    
    public void testThatRemoveAlarmReturnsFalseWhenTryingToRemoveAnAlarmThatWasNotPreviouslyAddedToTheList() {
        Alarm alarm = new Alarm();
        alarm.name = ALARM_NAME_FIXTURE;
        boolean result = alarmManager.removeAlarm(alarm);
        assertFalse(result);
    }
    
    public void testThatRemoveAlarmReturnsTrueWhenTryingToRemoveAnAlarmThatWasPreviouslyAddedToTheList() {
        Alarm alarm = createAndAddSingleAlarm();
        boolean result = alarmManager.removeAlarm(alarm);
        assertTrue(result);
    }
    
    public void testThatExceptionIsThrownWhenAddingNonNullAlarmWithNullContext() {
        boolean exceptionCaught = false;
        try {
            Alarm alarm = createAlarm();
            alarmManager.addAlarm(alarm, null);
        } catch (IllegalArgumentException e) {
            exceptionCaught = true;
        }
        
        assertTrue(exceptionCaught);
    }
    
    public void testThatAdjustAlarmToRetriggerAtEndOfSnoozePeriodIgnoresNullAlarm() {
        boolean exceptionCaught = false;
        
        try {
            boolean wasAdjusted = alarmManager.adjustAlarmToRetriggerAtEndOfSnoozePeriod(null, null);
            assertFalse(wasAdjusted);
        } catch (Exception e) {
            exceptionCaught = true;
        }
        
        assertFalse(exceptionCaught);
    }
    
    public void testThatAdjustAlarmToRetriggerAtEndOfSnoozePeriodReturnsTrueForNonNullAlarm() {
        Alarm alarm = createAlarm();
        
        boolean wasAdjusted = adjustAlarmToRetrigger(alarm);
        assertTrue(wasAdjusted);
    }
    
    public void testThatAdjustAlarmToRetriggerAtEndOfSnoozePeriodCorrectlyAdjustsTime() {
        Alarm alarm = createAlarm();
        long originalTriggerTime = alarm.triggerTime;
        
        boolean wasAdjusted = adjustAlarmToRetrigger(alarm);
        assertTrue(wasAdjusted);
        
        long snoozeTimeDurationInMilliseconds = alarmManager.calculateSnoozeDurationInMilliseconds();
        long expected = originalTriggerTime + snoozeTimeDurationInMilliseconds;
        long adjustedTriggerTime = alarm.triggerTime;
        
        assertEquals(expected, adjustedTriggerTime);
    }
    
    public void testThatResetAlarmIgnoresNullAlarm() {
        boolean exceptionCaught = false;
        
        try {
            Alarm alarm = null;
            boolean result = alarmManager.resetAlarm(alarm, null);
            assertFalse(result);
        } catch (Exception e) {
            exceptionCaught = true;
        }
        
        assertFalse(exceptionCaught);
    }
    
    public void testThatResetAlarmIgnoresDisabledAlarm() {
        Alarm alarm = createAlarm();
        alarm.enabled = false;
        
        Context context = getContext();
        long originalTriggerTime = alarm.triggerTime;
        
        boolean result = alarmManager.resetAlarm(alarm, context);
        removeStatusNotification(alarm);
        assertFalse(result);
        
        long triggerTimeAfterReset = alarm.triggerTime;
        
        assertEquals(originalTriggerTime, triggerTimeAfterReset);
    }
    
    public void testThatResetAlarmDoesNotAlterAlarmTriggerTime() {
        Alarm alarm = createAlarm(); // Note: we want the alarm enabled for this test
        Context context = getContext();
        
        long originalTriggerTime = alarm.triggerTime;
        
        boolean result = alarmManager.resetAlarm(alarm, context);
        removeStatusNotification(alarm);
        assertTrue(result);
        
        long triggerTimeAfterReset = alarm.triggerTime;
        
        assertEquals(originalTriggerTime, triggerTimeAfterReset);
    }
    
    public void testThatScheduleAlarmThrowsExceptionWhenContextIsNull() {
        boolean exceptionCaught = false;
        
        try {
            alarmManager.scheduleCountdownTimer(null, null);
        } catch (Exception e) {
            exceptionCaught = true;
        }
        
        assertTrue(exceptionCaught);
    }
    
    public void testThatScheduleAlarmDoesNotThrowExceptionWhenAlarmIsNull() {
        boolean exceptionCaught = false;
        
        try {
            Context context = getContext();
            alarmManager.scheduleCountdownTimer(context, null);
        } catch (Exception e) {
            exceptionCaught = true;
        }
        
        assertFalse(exceptionCaught);
    }
    
}
